/*
 * GenericHibernateDaoImpl.java
 *
 * Project: TECHWATCH - TESTING TECHNOLOGIES
 *
 * Copyright 2008 by ELCA Informatique SA
 * Av. de la Harpe 22-24, 1000 Lausanne 13
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of ELCA Informatique SA. ("Confidential Information"). You
 * shall not disclose such Confidential Information and shall
 * use it only in accordance with the terms of the license
 * agreement you entered into with ELCA.
 */

package ch.elca.training.dao.core.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;

import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.OptimisticLockingFailureException;

import ch.elca.training.dao.core.GenericHibernateDao;
import ch.elca.training.dao.core.utils.Reject;

/**
 * This class is a Hibernate-specific implementation of the ConvenienceGenericDao interface.
 * 
 * @param <T>
 *            The domain class the DAO is responsible for
 * @param <ID>
 *            The type of the domain class' identifier
 * 
 * @author Philipp Oser (POS)
 * @author Alex Mathey (AMA)
 * @author Jonas Hauenstein (JHN)
 */
public class GenericHibernateDaoImpl<T, ID extends Serializable> extends ConvenienceHibernateDaoSupport 
		implements GenericHibernateDao<T, ID>, InitializingBean {

    /**
     * The domain class this DAO is responsible for.
     */
    private Class<T> m_persistentClass;
    /**
     * The default hibernate {@link Order} to order results.
     */
    private Order[] m_defaultOrder = null;
	
    /**
     * Set up the Generic Dao. Auto-derive the parametrized type.
     */
    @SuppressWarnings("unchecked")
    public GenericHibernateDaoImpl() {
        try {
            this.m_persistentClass = (Class<T>) ((ParameterizedType) this.getClass()
                            .getGenericSuperclass()).getActualTypeArguments()[0];
        } catch (final Exception e) {
            // ignore issues (e.g. when the subclass is not a parametrized type)
            // in that case, one needs to set the persistencClass otherwise.
        }
    }
    
    /**
     * New: this callback is in general no longer required (the constructor figures the type out
     * itself).
     * 
     * @param c
     *            Mandatory. The domain class this DAO is responsible for.
     */
    public void setPersistentClass(final Class<T> c) {
        Reject.ifNull(c);
        this.m_persistentClass = c;
    }

    /**
     * @return Returns the domain class this DAO is responsible for.
     */
    public Class<T> getPersistentClass() {
        assert this.m_persistentClass != null;
        return this.m_persistentClass;
    }
    
	@SuppressWarnings("unchecked")
	public T findById(ID id) throws DataRetrievalFailureException, DataAccessException {
		return (T) this.getConvenienceHibernateTemplate().getByIdStrong(this.getPersistentClass(),
                id, this.getPersistentClassName());
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() throws DataAccessException {
		return this.getConvenienceHibernateTemplate().findByCriteria(this.getOrderedCriteria());
	}

	public void delete(T entity) throws OptimisticLockingFailureException, DataAccessException {
		this.getConvenienceHibernateTemplate().delete(entity);
	}

	public void deleteAll() throws OptimisticLockingFailureException, DataAccessException {
		final List<T> list = this.getAll();
        if (list.size() > 0) {
            this.delete(list);
        }
	}

	public T saveOrUpdate(T entity) throws DataAccessException, DataIntegrityViolationException, 
			OptimisticLockingFailureException {
		this.getConvenienceHibernateTemplate().saveOrUpdateStrong(entity, this.getPersistentClassName());
		return entity;
	}

	public void delete(Collection<T> entities) throws OptimisticLockingFailureException, DataAccessException {
		this.getConvenienceHibernateTemplate().deleteAll(entities);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(DetachedCriteria hibernateCriteria) throws DataAccessException {
		final ConvenienceHibernateTemplate template = this.getConvenienceHibernateTemplate();
        return template.findByCriteria(hibernateCriteria);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(DetachedCriteria hibernateCriteria, int firstResult, int maxResults) 
			throws DataAccessException {
		final ConvenienceHibernateTemplate template = this.getConvenienceHibernateTemplate();
        return template.findByCriteria(hibernateCriteria, firstResult, maxResults);
	}

	public int findCountByCriteria(DetachedCriteria hibernateCriteria) throws DataAccessException {
		final ConvenienceHibernateTemplate template = this.getConvenienceHibernateTemplate();
        return template.findCountByCriteria(hibernateCriteria);
	}

	public DetachedCriteria getOrderedCriteria() {
		final DetachedCriteria criteria = DetachedCriteria.forClass(this.getPersistentClass());
        return this.addOrder(this.makeDistinct(criteria));
	}
	
	/**
     * @param criteria
     *            the criteria to modify
     * @return the criteria enhanced with distinct restrictions
     */
    protected DetachedCriteria makeDistinct(final DetachedCriteria criteria) {
        criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
        return criteria;
    }
	
    /**
     * @param criteria
     *            the criteria to modify
     * @return the criteria enhanced with order constraints (if set using setDefaultOrder)
     */
    protected DetachedCriteria addOrder(final DetachedCriteria criteria) {
        if (this.m_defaultOrder != null) {
            for (final Order order : this.m_defaultOrder) {
                criteria.addOrder(order);
            }
        }
        return criteria;
    }
    
	/**
     * Returns the simple name of the persistent class this DAO is responsible for.
     * 
     * @return The simple name of the persistent class this DAO is responsible for.
     */
    protected String getPersistentClassName() {
        return this.getPersistentClass().getSimpleName();
    }
	
}
